package net.cyndeline.GameMap.dataStructure

import java.util.UUID
import scala.language.postfixOps
import com.escalatesoft.subcut.inject.BindingModule
import com.escalatesoft.subcut.inject.Injectable
import net.cyndeline.GameMap.Grid
import net.cyndeline.GameMap.LevelMap
import net.cyndeline.GameMap.Tile
import net.cyndeline.components.world.OnMapComponent
import net.cyndeline.components.world.OnMapComponent
import net.cyndeline.components.world.PositionComponent
import net.cyndeline.components.world.PositionComponent
import net.cyndeline.entitySystem.EntityRef
import net.cyndeline.UUIDFactory
import net.cyndeline.UUIDProvider
import net.cyndeline.components.world.OnMap
import net.cyndeline.components.world.Position

/**
 * A map that stores its content in a grid.
 */
case class GridBasedMap(implicit val bindingModule: BindingModule) extends LevelMap with Injectable {
  private var mapId: UUID = null
  var grid: Grid = null // Public for testing.
  private var nameStorage = "[Unfinished map]"
  
  override def build(id: UUID, newGrid: Grid, name: String) {
    mapId = id
    grid = newGrid
    nameStorage = name
  }
  
  override def id = mapId
  
  override def objectsAt(x: Int, y: Int): List[EntityRef] = grid.get(x, y) match {
    case Some(tile) => tile.content
    case None => Nil
  }
  
  @throws[IllegalArgumentException]
  override def addObject(entity: EntityRef): Boolean = {
    mapModify(Add, entity)
  }
  
  @throws[IllegalArgumentException]
  override def removeObject(entity: EntityRef): Boolean = {
    mapModify(Remove, entity)
  }
  
  /**
   * Adds or removes an entity from a tile in the grid specified
   * by the entities position component.
   */
  private def mapModify(action: Action, entity: EntityRef): Boolean = {
    val hasPosition = entity.hasComponent(classOf[Position])
    val hasOnMap = entity.hasComponent(classOf[OnMap])
    
    if (hasPosition && hasOnMap) {
      val onMap = entity.getComponent(classOf[OnMap]).get
      val position = entity.getComponent(classOf[Position]).get
      
      /* Throw exception if it's the wrong map. */
      wrongMapException(onMap.map)
      
      grid.get(position.x, position.y) match {
        case Some(tile) => {
          action match {
            case Add => return tile.addEntity(entity)
            case Remove => return tile.removeEntity(entity)
          }  
        }
        case None => return false
      }
      
    } else if (hasPosition) {
      missingMapException(entity)
    } else if (hasOnMap) {
      missingPositionException(entity)
    }
    
    true
  }
  
  override def width = grid.width
  override def height = grid.height
  
  override def name = nameStorage
  
  override def copy: GridBasedMap = {
    val id = (injectOptional[UUIDFactory] getOrElse new UUIDProvider()).random
    val cpy = new GridBasedMap()
    cpy.build(id, grid.copy, "Copy of " + nameStorage)
    cpy
  }
  
  private def wrongMapException(entityMap: LevelMap) {
    val otherMap = entityMap.id
    if (otherMap != mapId) {
      throw new IllegalArgumentException("Attempted to add a unit with map id " 
          + otherMap + " to map with id " + mapId)
    }
  }
  
  private def missingPositionException(entity: EntityRef) {
    throw new IllegalArgumentException("" + entity + " was added to the map " 
          + mapId + ", but lacks an onMap component")
  }
  
  private def missingMapException(entity: EntityRef) {
    throw new IllegalArgumentException("" + entity + " was added to the map " 
          + mapId + ", but lacks a position component")
  }
  
  private sealed trait Action
  private case object Add extends Action
  private case object Remove extends Action
}